//
//  Nick.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 14/06/2021.
//  Copyright © 2021 Thomas Harte. All rights reserved.
//

#pragma once

#include "ClockReceiver/ClockReceiver.hpp"
#include "Outputs/CRT/CRT.hpp"
#include <cstdint>

namespace Enterprise {

class Nick {
public:
	Nick(const uint8_t *ram);

	/// Writes to a Nick register; only the low two bits are decoded.
	void write(uint16_t address, uint8_t value);

	/// Reads from the Nick range. Nobody seems to be completely clear what
	/// this should return; I've set it up to return the last fetched video or mode
	/// line byte during periods when those things are being fetched, 0xff at all
	/// other times. Including during refresh, since I don't know what addresses
	/// are generated then.
	///
	/// This likely isn't accurate, but is the most accurate guess I could make.
	uint8_t read();

	void run_for(Cycles);
	Cycles get_time_until_z80_slot(Cycles after_period) const;

	void set_scan_target(Outputs::Display::ScanTarget *);
	Outputs::Display::ScanStatus get_scaled_scan_status() const;

	/// @returns The amount of time until the next potential change in interrupt output.
	Cycles next_sequence_point() const;

	/*!
		@returns The current state of the interrupt line — @c true for active;
			@c false for inactive.
	*/
	inline bool get_interrupt_line() const {
		return interrupt_line_;
	}

	/// Sets the type of output.
	void set_display_type(Outputs::Display::DisplayType);

	/// Gets the type of output.
	Outputs::Display::DisplayType get_display_type() const;

private:
	Outputs::CRT::CRT crt_;
	const uint8_t *const ram_;

	// CPU-provided state.
	uint8_t line_parameter_control_ = 0xc0;
	uint16_t line_parameter_base_ = 0x0000;
	uint16_t border_colour_ = 0;

	// Ephemerals, related to current video position.
	int horizontal_counter_ = 0;
	uint16_t line_parameter_pointer_ = 0x0000;
	bool should_reload_line_parameters_ = true;
	uint16_t line_data_pointer_[2];
	uint16_t start_line_data_pointer_[2];
	mutable uint8_t last_read_ = 0xff;

	// Current mode line parameters.
	uint8_t lines_remaining_ = 0x00;
	uint8_t two_colour_mask_ = 0xff;
	int left_margin_ = 0, right_margin_ = 0;
	const uint16_t *alt_ind_palettes[4] = {palette_, palette_, palette_, palette_};
	enum class Mode {
		Vsync,
		Pixel,
		Attr,
		CH256,
		CH128,
		CH64,
		Unused,
		LPixel,
	} mode_ = Mode::Vsync;
	bool is_sync_or_pixels_ = false;
	int bpp_ = 0;
	int column_size_ = 0;
	bool interrupt_line_ = true;
	int line_data_per_column_increments_[2] = {0, 0};
	bool vres_ = false;
	bool reload_line_parameter_pointer_ = false;

	// The destination for new pixels.
	static constexpr int allocation_size = 336;
	static_assert((allocation_size % 16) == 0, "Allocation size must be a multiple of 16");
	uint16_t *pixel_pointer_ = nullptr, *allocated_pointer_ = nullptr;

	// Output transitions.
	enum class OutputType {
		Sync, Blank, Pixels, Border, ColourBurst
	};
	void set_output_type(OutputType, bool force_flush = false);
	int output_duration_ = 0;
	OutputType output_type_ = OutputType::Sync;

	// Current palette.
	uint16_t palette_[16]{};

	// The first column with pixels on it; will be either 8 or 10 depending
	// on whether the colour burst is meaningful to the current display type.
	int first_pixel_window_ = 10;

	// Specific outputters.
	template <int bpp, bool is_lpixel> void output_pixel(uint16_t *target, int columns) const;
	template <int bpp, int index_bits> void output_character(uint16_t *target, int columns) const;
	template <int bpp> void output_attributed(uint16_t *target, int columns) const;
};

}
